using MongoDB.Driver;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.ESG.Entities;
using Sustainalytics.ESGRatings.BusinessLogic.Validators;
using System.Threading.Tasks;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using CompanyWeights = Sustainalytics.ESG.Entities.CompanyWeights;
using CompanyScores = Sustainalytics.ESGRatings.Computing.Entities.CompanyScores;
using Sustainalytics.ESGRatings.Computing.Entities;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;

namespace Sustainalytics.ESGRatings.BusinessLogic
{
    public struct CorporateDataInfo
    {
        public Guid Id { get; set; }

        public string CompanyName { get; set; }
    }

    public class CorporateDataLoader
    {
        private readonly IMongoCollection<CorporateData> corporateDataCollection;

        public CorporateDataLoader(IMongoCollection<CorporateData> corporateDataCollection)
        {
            this.corporateDataCollection = corporateDataCollection;
        }

        public virtual async Task ProcessAsync(Func<CorporateDataInfo, Task> process)
        {
            await corporateDataCollection.Find(x => x.ComprehensiveEsgResearchState.ResearchState == ResearchState.ResearchAvailable && x.ResearchParentId == null)
                .Project(x => new CorporateDataInfo { Id = x.Id, CompanyName = x.CompanyName })
                .ForEachAsync(x => process(x)).ConfigureAwait(false);
        }
    }

    public class ESGRawScoresWeightsValidator
    {
        readonly IValidator<CompanyWeights, CompanyScores> companyWeightsValidator;
        readonly CorporateDataLoader corporateDataManagement;
        readonly Action<string> logger;
        readonly IMongoCollection<RPCompanyESGComputationResult> companyScoreCollection;
        readonly IMongoCollection<CompanyWeights> weightsCollection;
        readonly IValidator<string> rawScoreValidator = new RawScoreValidator();

        public ESGRawScoresWeightsValidator(CorporateDataLoader corporateDataManagement, 
            IMongoCollection<CompanyWeights> weightsCollection,
            IMongoCollection<RPCompanyESGComputationResult> companyScoresCollection,
            IValidator<CompanyWeights, CompanyScores> companyWeightsValidator,
            Action<string> logger)
        {
            this.weightsCollection = weightsCollection;
            this.logger = logger;
            this.corporateDataManagement = corporateDataManagement;
            this.companyWeightsValidator = companyWeightsValidator;
            this.companyScoreCollection = companyScoresCollection;
        }


        public async Task<ConcurrentBag<RPESGResearchState>> ProcessAllSecurablesAsync(Dictionary<Guid, AccountEsgProfileMatrixAssociations> activeWeightMatrices, TemporaryDataSetBuilder builder)
        {
            int processedCompanies = 0;
            int processedCompanyWeights = 0;
            ConcurrentBag<RPESGResearchState> companiesResearchState = new ConcurrentBag<RPESGResearchState>();
            await corporateDataManagement.ProcessAsync(async (company) =>
                  {
                      Interlocked.Increment(ref processedCompanies);

                      var companyRawScores = await GetCompanyRawScores(company.Id).ConfigureAwait(false);
                      if (companyRawScores==null)
                      { return; }

                      builder.AddCompaniesRawScores(new[] { companyRawScores });

                      await weightsCollection.Find(x => x.CompanyId == company.Id && (activeWeightMatrices.Keys.Contains(x.MatrixId) || x.MatrixId == Guid.Empty)).ForEachAsync(
                           async (companyWeights) =>
                           {
                               Interlocked.Increment(ref processedCompanyWeights);
                               ICollection<string> validationResult = new List<string>();

                               if (activeWeightMatrices.TryGetValue(companyWeights.MatrixId, out AccountEsgProfileMatrixAssociations weightMatrix))
                               {
                                   if (!weightMatrix.MatrixName.Contains("Default"))
                                   {
                                       validationResult = await companyWeightsValidator.BrokenRulesAsync(companyWeights, companyRawScores).ConfigureAwait(false);

                                       if (validationResult.Any())
                                       {
                                           logger($"Company {company.CompanyName} - {validationResult.Aggregate((i, j) => i + "\n" + j)}");
                                       }
                                   }

                                   var companyResearchState = new RPESGResearchState(
                                       Guid.NewGuid(),
                                       company.Id,
                                       weightMatrix.EsgProfileId,
                                       weightMatrix.AccountId,
                                       weightMatrix.MatrixId,
                                       !validationResult.Any());
                                   companiesResearchState.Add(companyResearchState);
                                   builder.AddCompanyWeights(companyWeights.MatrixId, weightMatrix.AccountId, companyWeights, !validationResult.Any());
                               }
                           }).ConfigureAwait(false);
                  }).ConfigureAwait(false);

            logger($"Processed {processedCompanies} companies");
            logger($"Processed {processedCompanyWeights} weights");
            return companiesResearchState;
        }




        public async Task<CompanyScores> GetCompanyRawScores(Guid companyId)
        {
            var companyScore = await companyScoreCollection.Find(x => x.Id == companyId).FirstOrDefaultAsync().ConfigureAwait(false);

            if (companyScore==null)
            {
                return null;
            }
            var validaRawScores = companyScore
                .Indicators
                .Where(rawscore => rawScoreValidator.IsValid(rawscore.Code))
                .Select(inner => new IndicatorDefaultScore(inner.Code, Convert.ToInt32(inner.RawScore), Convert.ToSingle(inner.Weight), Convert.ToSingle(inner.WeightedScore)));
            var result = new CompanyScores(companyId, validaRawScores)
            {
                OverallRating = await GetCompanyThemeRating(companyScore, x => x.OverallPerformance).ConfigureAwait(false),
                EnvironmentRating = await GetCompanyThemeRating(companyScore, x => x.EnvironmentPerformance).ConfigureAwait(false),
                SocialRating = await GetCompanyThemeRating(companyScore, x => x.SocialPerformance).ConfigureAwait(false),
                GovernanceRating = await GetCompanyThemeRating(companyScore, x => x.GovernancePerformance).ConfigureAwait(false),
                DisclosureRating = await GetCompanyThemeRating(companyScore, x => x.DisclosureScore).ConfigureAwait(false),
                PreparednessRating = await GetCompanyThemeRating(companyScore, x => x.PreparednessScore).ConfigureAwait(false),
                QualitativePerformanceRating = await GetCompanyThemeRating(companyScore, x => x.QualitativePerformanceScore).ConfigureAwait(false),
                QuantitativePerformanceRating = await GetCompanyThemeRating(companyScore, x => x.QuantitativePerformanceScore).ConfigureAwait(false)
            };
            return result;

        }

        private Task<Rating> GetCompanyThemeRating(RPCompanyESGComputationResult companyScore, Func<RPCompanyESGComputationResult, RPPerformance> getThemeRating)
        {
            RPPerformance themeScore = getThemeRating(companyScore);
            if (themeScore==null )
            {
                return Task.FromResult(new Rating(0f, 0f));
            }
            var result = new Rating(Convert.ToSingle(themeScore.Score), Convert.ToSingle(themeScore.Weight));
            return Task.FromResult(result);
        }
    }
}
